import { promises as fs, watchFile, unwatchFile } from "fs";
import path from "path";
import { app } from "electron";
import { guessFileLocation } from "./utils";
import { ServerEvents } from "../server";
import { Logger } from "../RemoteLogger";

const POSSIBLE_PATH =
  process.platform === "win32"
    ? [
        "C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile 2\\logs\\Client.txt",
        "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2\\logs\\Client.txt",
      ]
    : process.platform === "linux"
      ? [
          path.join(
            app.getPath("home"),
            ".wine/drive_c/Program Files (x86)/Grinding Gear Games/Path of Exile 2/logs/Client.txt",
          ),
          path.join(
            app.getPath("home"),
            ".local/share/Steam/steamapps/common/Path of Exile 2/logs/Client.txt",
          ),
        ]
      : process.platform === "darwin"
        ? [
            path.join(
              app.getPath("home"),
              "Library/Caches/com.GGG.PathOfExile/Logs/Client.txt",
            ),
          ]
        : [];

export class GameLogWatcher {
  private _wantedPath: string | null = null;
  get actualPath() {
    return this._state?.path ?? null;
  }

  private _state: {
    offset: number;
    path: string;
    file: fs.FileHandle;
    isReading: boolean;
    readBuff: Buffer;
  } | null = null;

  constructor(
    private server: ServerEvents,
    private logger: Logger,
  ) {}

  async restart(logFile: string) {
    if (this._wantedPath !== logFile) {
      this._wantedPath = logFile;
      if (this._state) {
        unwatchFile(this._state.path);
        await this._state.file.close();
        this._state = null;
      }
    } else {
      return;
    }

    if (!logFile.length) {
      const guessedPath = await guessFileLocation(POSSIBLE_PATH);
      if (guessedPath != null) {
        logFile = guessedPath;
      } else {
        return;
      }
    }

    try {
      const file = await fs.open(logFile, "r");
      const stats = await file.stat();
      watchFile(logFile, { interval: 450 }, this.handleFileChange.bind(this));
      this._state = {
        path: logFile,
        file,
        offset: stats.size,
        isReading: false,
        readBuff: Buffer.allocUnsafe(64 * 1024),
      };
    } catch {
      this.logger.write("error [GameLogWatcher] Failed to watch file.");
    }
  }

  private handleFileChange() {
    if (this._state && !this._state.isReading) {
      this._state.isReading = true;
      this.readToEOF();
    }
  }

  private async readToEOF() {
    if (!this._state) return;

    const { file, readBuff, offset } = this._state;
    const { bytesRead } = await file.read(readBuff, 0, readBuff.length, offset);

    if (bytesRead) {
      const str = readBuff.toString("utf8", 0, bytesRead);
      const lines = str
        .split("\n")
        .map((line) => line.trim())
        .filter((line) => line.length);
      this.server.sendEventTo("broadcast", {
        name: "MAIN->CLIENT::game-log",
        payload: { lines },
      });
    }

    if (bytesRead) {
      this._state.offset += bytesRead;
      this.readToEOF();
    } else {
      this._state.isReading = false;
    }
  }
}
